use super::*;
use std::iter::zip;

impl MapGen {
    #[inline]
    fn divide_area(&mut self) -> GenResult<Vec<(usize, usize, usize)>> {
        let mut vis = self.new_vis();
        let (m, n) = self.base_size();

        let mut cur_index = 0;
        let mut list = Vec::new();
        for i in 0..m {
            for j in 0..n {
                if !vis[i][j] {
                    let bfs_option = BfsOption {
                        startx: i,
                        starty: j,
                        is_rand: false,
                        arg: (self.base[i][j].height, self.base[i][j].is_land, cur_index),
                        vistied_map: Some(&mut vis),
                        init: Box::new(|gen, (x, y), arg| {
                            gen.base[x][y].belong_to = arg.2;
                            1
                        }),
                        filter: Box::new(|gen, (x, y), arg| {
                            gen.base[x][y].is_land == arg.1 && gen.base[x][y].height == arg.0
                        }),
                        block_action: Box::new(|gen, (x, y), count, arg| {
                            *count += 1;
                            gen.base[x][y].belong_to = arg.2;
                            true
                        }),
                        end_action: Box::new(|_, _, _| Ok(())),
                    };
                    let count = self.std_bfs(bfs_option)?;
                    list.push((count, i, j));
                    cur_index += 1;
                }
            }
        }
        Ok(list)
    }
    #[inline]
    fn close_to_area(&self, (x, y): (usize, usize), index: usize) -> bool {
        if self.base[x][y].belong_to == index {
            return false;
        }

        let (m, n) = self.base_size();

        let height = self.base[x][y].height;

        let sx = x as i32;
        let sy = y as i32;
        for (dx, dy) in zip(DX.iter(), DY.iter()) {
            let (nx, ny) = {
                let (nx, ny) = (sx + *dx, sy + *dy);
                if point_in_map((nx, ny), (m, n)) {
                    (nx as usize, ny as usize)
                } else {
                    continue;
                }
            };
            if self.base[nx][ny].belong_to == index
                && self.base[nx][ny].captured == Captured::None
                && self.base[nx][ny].height == height + 1
            {
                return true;
            }
        }
        false
    }
    #[inline]
    fn block_set_ramp(&mut self, (x, y): (usize, usize), index: usize) {
        let (m, n) = self.base_size();

        let sx = x as i32;
        let sy = y as i32;

        let height = self.base[x][y].height;

        let mut list = Vec::with_capacity(4);
        for ((dx, dy), ramp) in zip(zip(DX.iter(), DY.iter()), RAMP_SIDE_OUT) {
            let (nx, ny) = {
                let (nx, ny) = (sx + *dx, sy + *dy);
                if point_in_map((nx, ny), (m, n)) {
                    (nx as usize, ny as usize)
                } else {
                    continue;
                }
            };
            if self.base[nx][ny].belong_to == index && self.base[nx][ny].height == height + 1 {
                list.push(ramp);
            }
        }

        if list.len() == 0 {
            self.display_base();
            panic!("Fail to set ramp at ({},{}) with index {} !", x, y, index);
        }

        let selected = self.rng.gen_range(0..list.len());
        self.base[x][y].ramp = list[selected];
        self.base[x][y].captured = Captured::Ramp;

        // println!("({},{}) ramp : {:?}", x, y, self.base[x][y].ramp);
    }
    #[inline]
    pub fn gen_ramp(&mut self) -> GenResult<()> {
        let area_list = self.divide_area()?;
        let (m, n) = self.base_size();

        for (owner, (area, x, y)) in area_list.iter().enumerate() {
            if self.base[*x][*y].is_land == LandOrWater::Water {
                continue;
            }

            let mut available = Vec::new();
            for i in 0..m {
                for j in 0..n {
                    if self.close_to_area((i, j), owner) {
                        available.push((i, j));
                    }
                }
            }

            // debug_assert!(
            //     available.len() != 0,
            //     "This area {} cannot get to other area !",
            //     owner
            // );

            if available.len() == 0 {
                println!("Area {} is the top ", owner);
                continue;
            }

            let ramp_count = self.config.ramp_to_area.get(*area, &mut self.rng);
            for _ in 0..ramp_count {
                if available.len()==0{
                    break;
                }

                let selected = self.rng.gen_range(0..available.len());
                let (x, y) = available[selected];
                available.remove(selected);

                self.block_set_ramp((x, y), owner);
            }
        }

        Ok(())
    }
}
